let system = require("./system.js");
let config = require("./config.js");

module.exports = {
    // 查找当前有多少个相同的控件
    countWidget(functionName, text,) {
        // 查找所有 ImageView 控件
        let imageViews = functionName(text).find(10000);

        if (imageViews.empty()) {
            system.Log("没有找到任何 控件--" + text);
        } else {
            system.Log("找到 " + imageViews.size() + " 个 ImageView");

            // 遍历输出每个控件信息
            for (let i = 0; i < imageViews.size(); i++) {
                let view = imageViews.get(i);
                log("第 " + (i + 1) + " 个 ImageView: " + view);
            }
        }
    },
    // 获取当前脚本的路径
    getScriptPath() {
        // 输出当前运行脚本的绝对路径
        system.Log("当前脚本路径: " + engines.myEngine().source.toString());

        // 输出当前工作目录
        system.Log("当前工作目录: " + files.cwd());
        return files.cwd()
    },
    /**
     * 同步发送 HTTP 请求
     * @param {string} url - 请求地址
     * @param {object} options - 配置对象 { method, headers, body }
     * @returns {object|null} - 返回 JSON 或文本
     * 使用方法：let data2 = httpRequest("https://httpbin.org/post", {
     *     method: "POST",
     *     headers: { "Content-Type": "application/json" },
     *     body: JSON.stringify({ task: "prepare_pay" })
     * });
     */
    httpRequest(url, options) {
        options = options || {};
        let method = (options.method || "GET").toUpperCase();
        let headers = options.headers || {};
        let body = options.body || null;

        try {
            let res;
            if (method === "GET") {
                res = http.get(url, {headers: headers});
            } else if (method === "POST") {
                res = http.post(url, body, {headers: headers});
            } else {
                // Auto.js 6 支持自定义请求
                res = http.request(url, {
                    method: method,
                    headers: headers,
                    body: body,
                });
            }

            if (res.statusCode === 200) {
                system.Log("请求成功: " + res);
                // 优先尝试 JSON
                try {
                    return res.body.json();
                } catch (e) {
                    return res.body.string();
                }
            } else {
                system.Log("请求失败: " + res.statusCode);
                return null;
            }
        } catch (e) {
            system.Log("请求异常: " + e);
            return null;
        }
    },
    // 截图控件并保存
    captureWidget(widget, fileName) {
        if (!widget) {
            system.Log("控件不存在，无法截图");
            return "";
        }

        // // 请求截图权限
        // if (!requestScreenCapture()) {
        //     system.Log("请求截图权限失败");
        //     return "";
        // }
        sleep(1000);

        try {
            // 获取控件区域
            let rect = widget.bounds();
            // captureScreen()需要权限，但automator.captureScreen()不需要。同样是截图
            // let img = captureScreen(); // 截全屏
            let img = automator.captureScreen(); // 截全屏
            let bmp = images.clip(img, rect.left, rect.top, rect.width(), rect.height()); // 裁剪控件区域
            // 生成文件名
            let dir = files.cwd(); // 脚本同一级目录
            if (!fileName) {
                fileName = "widget_" + new Date().getTime() + ".png";
            }
            let filePath = dir + "/" + fileName;

            // 保存图片
            images.save(bmp, filePath, "png", 100);

            bmp.recycle();
            img.recycle();

            system.Log("控件截图已保存: " + filePath);
            return filePath;
        } catch (e) {
            system.Log("截图失败: " + e);
            return "";
        }
    },
    // 截图并上传图片
    uploadWidgetImage(widget, fileName, task_no) {
        // 1. 截图
        let filePath = this.captureWidget(widget, fileName);
        if (!filePath) {
            system.Log("截图失败，无法上传");
            return null;
        }

        // 2. 构造上传请求
        let url = config.SaveOrderImageUrl();

        // system.Log("上传图片: " + task_no);

        try {
            // POST multipart/form-data 上传文件；必须切换回aotojs6的app才能发送大文件，他依赖前台。
            // 如果在抖音页面是发送不成功的，连 system.Log("上传返回: " + res.body.string());都走不到，然后就会超时
            let order_num = system.paste().length === 19 ? system.paste() : ""
            system.Log("找到 '商品主页',准备截图。复制的订单号:" + order_num)
            app.launchApp("AutoJs6");
            let res = http.postMultipart(
                url,
                {image: open(filePath), task_no: task_no, order_num: order_num},
                {timeout: 60000} // 60 秒
            );

            system.Log("上传返回: " + res.body.string());
            sleep(800);
            app.launchApp(config.targetAppName);
            return res.body.string();
        } catch (e) {
            system.Log("上传失败: " + e);
            return null;
        }
    },
    // 智能点击函数：先尝试控件点击，不行就用坐标点击
    forceClick(w, content_click = false, double_click = false) {
        if (!w) return false;
        let x;
        let y;
        let b = w.bounds();
        if (b.width() <= 4 || b.height() <= 4) {
            // 控件太小，直接点中心
            x = b.centerX();
            y = b.centerY();
        } else if (content_click) {
            x = b.centerX();
            y = b.centerY();
        } else {
            x = random(b.left + 2, b.right - 2);
            y = random(b.top + 2, b.bottom - 2);
        }
        // 无障碍点击失败 → 用坐标点击
        try {
            click(x, y);
            if (double_click) {
                // 这个主要用来下单卡风控bug的。点击三次是因为有的可能是视频。
                sleep(50);
                click(config.video_play_pos.x, config.video_play_pos.y);
                sleep(50);
                click(config.video_play_pos.x, config.video_play_pos.y);
                sleep(50);
                click(config.video_play_pos.x, config.video_play_pos.y);
                sleep(50);
                click(config.video_play_pos.x, config.video_play_pos.y);
                sleep(50);
                click(config.video_play_pos.x, config.video_play_pos.y);
            }
            // press(x, y, random(50, 200));  // 按压 50~200 毫秒再松开；主要做抬起动作。
            // system.Log("无障碍点击成功: " + w.bounds());
            return true
        } catch (e) {
            system.Log("坐标点击失败: " + e);
            return false;
        }
    },
    // 调出后台管理/最近任务;目前几台新测试机都可以用
    // 注意:三个导航按键如果出现则不能用此方法，因为该方法是手势打开，如果是三个按钮，手势是不生效的。
    goBackground() {
        system.goHome()
        sleep(1000)
        // this.swipeByPercent(0.5, 0.99, 0.5, 0.3, 800);
        utils.humanSwipe(device.width * 0.5, device.height * 0.99, device.width * 0.5, device.height * 0.3, 800)
        sleep(1000);
        // 清楚后台任务
        this.forceFindControl({functionName: id, text: "clearAnimView"})
    },
    // 智能寻找控件函数;
    forceFindControl({
                         functionName,
                         text,
                         click = true,
                         double_click = false,
                         awaitTime = 10000,
                         sleepTime = 20,
                         is_loop = false,
                         content_click = false,
                         index = 0,
                         excludeClassNames = ["ViewGroup", "LynxFlattenUI"]
                     }) {
        let Control = null;
        let result = "";
        let num = is_loop ? 3 : 1;

        // 定义过滤函数，只返回有效控件;屏蔽虚拟控件
        function isValidControl(w, excludeClassNames = []) {
            if (!w) return false;

            let b = w.bounds();

            // 动态过滤 className
            let className = w.className() || "";
            let excluded = excludeClassNames.some(name => className.includes(name));

            return (
                b.width() > 0 &&
                b.height() > 0 &&
                w.visibleToUser() &&
                w.centerX() > 0 &&
                w.centerY() > 0 &&
                !excluded
            );
        }

        for (let i = 0; i < num; i++) {
            Control = functionName(text).filter(w => isValidControl(w, excludeClassNames)).findOne(awaitTime);
            if (index) {
                Control = null;
                result = functionName(text).filter(w => isValidControl(w, excludeClassNames)).find();
                // system.Log(result.size());
                if (result.size() > 0) {
                    let normalizedIndex = index < 0 ? result.length + index : index;
                    if (normalizedIndex >= 0 && normalizedIndex < result.length) {
                        Control = result[normalizedIndex];
                    }
                }

            }
            if (Control) break;
        }


        if (Control) {
            if (click) {
                sleep(random(sleepTime, sleepTime + 100));
                this.forceClick(Control, content_click, double_click);
            }
            return Control;
        } else {
            system.Log("未找到有效控件: " + text);
            return null;
        }
    },

    // 返回至抖音主页；如果是主页就不操作。不是则返回；
    ReturnDYHome() {
        // 循环10次
        for (let i = 0; i < 10; i++) {
            // 寻找返回按钮
            let imageViews = text("商城").findOne(3000);

            if (imageViews) {
                system.Log("找到首页---");
                click(imageViews.centerX(), imageViews.centerY());
                return true;
            } else {
                back(); // 模拟按下系统返回键
                system.Log("没有找到首页，返回一层");
            }
            sleep(800);
        }
        app.launchApp(config.targetAppName);
        return false;

    },
    // 我的订单页面里面点击搜索，输入搜索内容
    searchOrder(_string) {
        let inputField = text('搜索我的订单').findOne(4000)
        if (inputField) {
            system.Log("找到输入框1" + _string);
            click(inputField.centerX(), inputField.centerY());
            sleep(500);
            setText(_string)
            // let aa = shell('am broadcast -a ADB_INPUT_TEXT --es msg "6945617836663576549"',false)
            // shell('am broadcast -a ADB_INPUT_TEXT --es msg ' + _string,true)
            sleep(300);
            this.forceFindControl({functionName: text, text: "搜索"})
            return true;
        }
        let inputField2 = className('com.bytedance.ies.xelement.input.LynxInputViewLegacy').findOne(4000)
        if (inputField2) {
            system.Log("找到输入框2" + _string);
            setClip(_string)
            click(inputField2.centerX(), inputField2.centerY());
            sleep(500);
            click(config.pos1.x, config.pos1.y)
            sleep(500);
            click(config.pos2.x, config.pos2.y)
            this.forceFindControl({functionName: text, text: "搜索"})
            return true;
        }
        system.Log("未找到输入框");
        return false;
    },
    //  控件滑动；; swipeInWidget(className("FrameLayout").findOne(5000), 0.5, 0.9, 0.5, 0.1, 500);  // 百分比: 0.5=中间, 0.9=底部
    swipeInWidget(widget, fromXPercent, fromYPercent, toXPercent, toYPercent, duration) {
        if (!widget) {
            system.Log("控件不存在，无法滑动");
            return false;
        }
        let rect = widget.bounds();

        // 起点：在控件内
        let startX = rect.left + rect.width() * fromXPercent;
        let startY = rect.top + rect.height() * fromYPercent;

        // 终点：在整个屏幕范围;device代表手机
        let endX = device.width * toXPercent;
        let endY = device.height * toYPercent;

        return swipe(startX, startY, endX, endY, duration);
    },
    // 屏幕滑动
    // swipeByPercent(0.5, 0.8, 0.5, 0.2, 500)----从屏幕中间靠下 (50%, 80%) 向上滑到 (50%, 20%)，时长 500ms
    swipeByPercent(fromXPercent, fromYPercent, toXPercent, toYPercent, duration) {
        let startX = device.width * fromXPercent;
        let startY = device.height * fromYPercent;
        let endX = device.width * toXPercent;
        let endY = device.height * toYPercent;

        return swipe(startX, startY, endX, endY, duration);
    },
    /**
     * 模拟人类轨迹滑动
     * @param {number} startX 起点X（屏幕坐标）
     * @param {number} startY 起点Y（屏幕坐标）
     * @param {number} endX 终点X
     * @param {number} endY 终点Y
     * @param {number} duration 滑动总时长，毫秒
     * 用法：从屏幕中间下滑到顶部--humanSwipe(device.width * 0.5, device.height * 0.8, device.width * 0.5, device.height * 0.2, 800);
     * 用法2：绝对值滑动--humanSwipe(540, 1600, 540, 400, 800);
     */
    humanSwipe(startX, startY, endX, endY, duration) {
        // 随机化控制点
        function randOffset(range) {
            return (Math.random() - 0.5) * 2 * range;
        }

        // 贝塞尔曲线函数
        function bezier(t, p0, p1, p2, p3) {
            let cX = 3 * (p1.x - p0.x),
                bX = 3 * (p2.x - p1.x) - cX,
                aX = p3.x - p0.x - cX - bX;

            let cY = 3 * (p1.y - p0.y),
                bY = 3 * (p2.y - p1.y) - cY,
                aY = p3.y - p0.y - cY - bY;

            let x = aX * Math.pow(t, 3) + bX * Math.pow(t, 2) + cX * t + p0.x;
            let y = aY * Math.pow(t, 3) + bY * Math.pow(t, 2) + cY * t + p0.y;

            return {x: x, y: y};
        }

        let p0 = {x: startX, y: startY};
        let p3 = {x: endX, y: endY};

        let p1 = {x: startX + randOffset(100), y: startY + randOffset(100)};
        let p2 = {x: endX + randOffset(100), y: endY + randOffset(100)};

        let steps = Math.max(Math.floor(duration / 5), 10); // 每5ms一次
        let path = [];

        for (let i = 0; i <= steps; i++) {
            let t = i / steps;
            path.push(bezier(t, p0, p1, p2, p3));
        }

        // 执行滑动
        gesture(duration, path.map(p => [p.x, p.y]));
    },

    // 截取商品订单图片
    getGoodsOrderImage(image_name = "image", task_no = "555") {
        // 在autojs6中开去布局模式，然后选择控件，点击‘在布局层次中查看’能看到是在第二个就是商品控件；
        // 必须在某个商品详情页面才能截取
        let imageViews = className('android.view.View').find()[1];
        if (imageViews) {
            this.uploadWidgetImage(imageViews, image_name + ".png", task_no)
        } else {
            system.Log("没有找到任何控件，无法截取图片");

        }
    },
    //  支付宝输入密码；input_zfb_password("556688")
    input_zfb_password(pws) {
        for (let i = 0; i < pws.length; i++) {
            let c = pws.charAt(i);
            let result = this.forceFindControl({
                functionName: text,
                text: c,
                click: true,
                awaitTime: 5000,
                is_loop: true,
                content_click: true
            });
            if (!result) {
                // 如果任何一个字符找不到对应控件，立即返回 false
                system.Log("密码输入失败")
                return false;
            }
        }
        return true;
    },
    // 找不到控件的失败回调
    fail_callback(crl, pay_num, url, msg = "任务失败一个，返回桌面，等待 5 秒：") {
        if (!crl) {
            system.Log(msg + pay_num);
            app.launchApp("AutoJs6");
            utils.httpRequest(url + `&msg=${msg}`, {
                method: "GET"
            });
            this.goBackground()
            // this.ReturnDYHome()
            // sleep(800);
            // home();
            sleep(3000);
            return false;
        }
        return true;
    }
};


